#pragma once

namespace VirtualPedal
{
	using namespace System::Collections;
	using namespace System::Collections::Generic;
	using namespace System::Runtime::InteropServices;
	using namespace System::Threading;

	public ref class DeviceDescriptor
	{
	public:
		property String^ Name;
		property String^ FriendlyName;

		virtual String^ ToString() override
		{
			return FriendlyName;
		}
	};

	public ref class RingBuffer
	{
		array<Byte>^ m_Array;
		UInt32 m_WriteCursor;
		UInt32 m_ReadCursor;

	public:

		property UInt32 WriteCursor
		{
			UInt32 get() { return m_WriteCursor; }
		}

		property UInt32 ReadCursor
		{
			UInt32 get() { return m_ReadCursor; }
		}

		property UInt32 BufferSize
		{
			UInt32 get() { return m_Array->Length; }
		}

		RingBuffer(UInt32 maxLength)
		{
			m_WriteCursor = 0;
			m_ReadCursor = 0;
			m_Array = gcnew array<Byte>(maxLength);
		}

		Int32 ReadAll(array<Byte>^ targetArray)
		{
			Monitor::Enter(this);
			try
			{
				Int32 numToCopy = 0;
				Int32 startOverLength = 0;
				Int32 firstCopyLength = 0;
				numToCopy = m_WriteCursor - m_ReadCursor;
				if(numToCopy < 0)
				{
					startOverLength = m_WriteCursor;
					numToCopy += m_Array->Length;
					firstCopyLength = numToCopy - startOverLength;
				}
				else
				{
					firstCopyLength = numToCopy;
				}

				if(targetArray->Length < numToCopy)
				{
					return -1;
				}
				if(firstCopyLength > 0)
				{
					Array::Copy(m_Array, m_ReadCursor, targetArray, 0, firstCopyLength);
				}
				if(startOverLength > 0)
				{
					Array::Copy(m_Array, 0, targetArray, firstCopyLength, startOverLength);
				}
				m_ReadCursor = m_WriteCursor;
				return numToCopy;
			}
			finally
			{
				Monitor::Exit(this);
			}
		}

		Int32 GetAvailableCount()
		{
			Monitor::Enter(this);
			try
			{
				Int32 numToCopy = m_WriteCursor - m_ReadCursor;
				if(numToCopy < 0)
				{
					numToCopy += m_Array->Length;
				}
				return numToCopy;
			}
			finally
			{
				Monitor::Exit(this);
			}
		}

	internal:

		Int32 Read(BYTE* pTarget, UINT32 numToRead)
		{
			Monitor::Enter(this);
			try
			{
				numToRead = Math::Min((Int32)GetAvailableCount(), (Int32)numToRead);

				Int32 startOverLength = 0;
				Int32 firstCopyLength = 0;
				if(numToRead + m_ReadCursor >= (UINT32)m_Array->Length)
				{
					startOverLength = numToRead + m_ReadCursor - m_Array->Length;
					firstCopyLength = numToRead - startOverLength;
				}
				else
				{
					firstCopyLength = numToRead;
				}

				if(firstCopyLength > 0)
				{
					Marshal::Copy(m_Array, m_ReadCursor, IntPtr(pTarget), firstCopyLength);
					m_ReadCursor = m_ReadCursor + firstCopyLength;
				}
				if(startOverLength > 0)
				{
					Marshal::Copy(m_Array, 0, IntPtr(pTarget + firstCopyLength), startOverLength);
					m_ReadCursor = startOverLength;
				}
				
				return numToRead;
			}
			finally
			{
				Monitor::Exit(this);
			}
		}

		void Write(Byte* pSource, UInt32 numBytes)
		{
			Monitor::Enter(this);
			try
			{
				IntPtr source = IntPtr(pSource);
				Int32 nStartOverLength = m_WriteCursor + numBytes - m_Array->Length;
				nStartOverLength = Math::Max(0, nStartOverLength);
				Int32 firstCopyLength = numBytes - nStartOverLength;
				Marshal::Copy(source, m_Array, (Int32)m_WriteCursor, firstCopyLength);
				m_WriteCursor = m_WriteCursor + firstCopyLength;
				if(nStartOverLength > 0)
				{
					Marshal::Copy(source, m_Array, 0, nStartOverLength);
					m_WriteCursor = nStartOverLength;
				}

				//FIXME:
				//Handle buffer starvation here.
			}
			finally
			{
				Monitor::Exit(this);
			}
		}
	};

	public enum class EWaveFormat
	{
		PCM = 0,
		IEEE_FLOAT,
		DRM,
		ALAW,
		MULAW,
		ADPCM,
		EXTENSIBLE,
		UNKNOWN,
	};

	public ref class AudioStreamDescriptor
	{
	public:
		property EWaveFormat WaveFormat;
		property UInt32 SampleFrequency;
		property UInt16 BitsPerSample;
		property UInt16 ValidBitsPerSample;
		property UInt16 NumChannels;
		property System::Guid SubFormatGuid;

		property Int32 NormalizationFactor
		{
			Int32 get() { return (1 << (ValidBitsPerSample + 1)) - 1; }
		}

		AudioStreamDescriptor()
		{}

		AudioStreamDescriptor(WAVEFORMATEXTENSIBLE* waveFmt)
		{
			SampleFrequency = waveFmt->Format.nSamplesPerSec;
			BitsPerSample = waveFmt->Format.wBitsPerSample;
			NumChannels = waveFmt->Format.nChannels;
			ValidBitsPerSample = waveFmt->Samples.wValidBitsPerSample;
			WaveFormat = AudioStreamDescriptor::GetWaveFormatFromTag(waveFmt->Format.wFormatTag);
			SubFormatGuid = (System::Guid)Marshal::PtrToStructure(IntPtr(&waveFmt->SubFormat), System::Guid::typeid);
		}

		void GetWAVEFORMATEXTENSIBLE(WAVEFORMATEXTENSIBLE* pOutWaveFmt)
		{
			ZeroMemory(pOutWaveFmt, sizeof(WAVEFORMATEXTENSIBLE));
			pOutWaveFmt->Format.wFormatTag = GetTagFromWaveFormat(WaveFormat);
			pOutWaveFmt->Format.nSamplesPerSec = SampleFrequency;
			pOutWaveFmt->Format.nChannels = NumChannels;
			pOutWaveFmt->Format.wBitsPerSample = BitsPerSample;
			pOutWaveFmt->Format.nBlockAlign = NumChannels * BitsPerSample / 8;
			pOutWaveFmt->Format.nAvgBytesPerSec = SampleFrequency * pOutWaveFmt->Format.nBlockAlign;
			pOutWaveFmt->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
			pOutWaveFmt->Samples.wValidBitsPerSample = ValidBitsPerSample;
			pOutWaveFmt->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
			Marshal::StructureToPtr(SubFormatGuid, IntPtr(&pOutWaveFmt->SubFormat), false);
		}

		static EWaveFormat GetWaveFormatFromTag(DWORD formatTag)
		{
			switch(formatTag)
			{
			case WAVE_FORMAT_PCM: return EWaveFormat::PCM;
			case WAVE_FORMAT_IEEE_FLOAT: return EWaveFormat::IEEE_FLOAT;
			case WAVE_FORMAT_DRM: return EWaveFormat::DRM;
			case WAVE_FORMAT_ALAW: return EWaveFormat::ALAW;
			case WAVE_FORMAT_MULAW: return EWaveFormat::MULAW;
			case WAVE_FORMAT_ADPCM: return EWaveFormat::ADPCM;
			case WAVE_FORMAT_EXTENSIBLE: return EWaveFormat::EXTENSIBLE;
			default: return EWaveFormat::UNKNOWN;
			}
		}

		static array<WORD>^ formatMap = {
			WAVE_FORMAT_PCM,
			WAVE_FORMAT_IEEE_FLOAT,
			WAVE_FORMAT_DRM,
			WAVE_FORMAT_ALAW,
			WAVE_FORMAT_MULAW,
			WAVE_FORMAT_ADPCM,
			WAVE_FORMAT_EXTENSIBLE,
			WAVE_FORMAT_UNKNOWN,
		};

		static WORD GetTagFromWaveFormat(EWaveFormat format)
		{
			return formatMap[(Int32)format];
		}
	};

	public ref class AudioPort
	{
		AudioStreamDescriptor^ m_Descriptor;
		RingBuffer^ m_RingBuffer;

	public:
		AudioPort(AudioStreamDescriptor^ descriptor)
		{
			const UInt32 BufferSeconds = 5;
			m_Descriptor = descriptor;
			m_RingBuffer = gcnew RingBuffer(BufferSeconds * m_Descriptor->SampleFrequency * m_Descriptor->BitsPerSample / 8);
		}

		property RingBuffer^ DataBuffer
		{
			RingBuffer^ get() { return m_RingBuffer; }
		}

		property AudioStreamDescriptor^ Descriptor
		{
			AudioStreamDescriptor^ get() { return m_Descriptor; }
		}
	};

	public ref class AudioDeviceBase abstract
	{
	protected:
		float m_Gain;
		AudioDeviceBase()
		{
			m_Gain = 0.0f;
		}
	public:

		virtual void Init() abstract;
		virtual void Open(DeviceDescriptor^ desc) abstract;
		virtual void Close() abstract;
		virtual property List<DeviceDescriptor^>^ Devices
		{
			virtual List<DeviceDescriptor^>^ get() abstract;
		}
		virtual property bool IsOpened
		{
			virtual bool get() abstract;
		}
		property float Gain
		{
			void set(float value) { m_Gain = Math::Max(Math::Min(value, 10.0f), -10.0f); }
			float get() { return m_Gain; }
		}
	};
}